<!--
    by: yuanbiao 2020年12月05日
    name: x-audio
    notes:
-->
<template>
  <div ref="xAudio" :class="[`${prefixCls}`, `${prefixCls}-${size}`]"
    @keydown.prevent.stop="keydownHandler"
    tabindex="1"
  >
    <audio ref="audio" :class="[`${prefixCls}__audio`, 'hide']"
      :autoplay="statusCtrl.autoplay"
      :preload="statusCtrl.preload"
      :src="statusCtrl.currentSrc"
      crossOrigin="anonymous"
      @play="playCallback"
      @canplay="canplayCallback"
      @pause="pauseCallback"
      @ended="endedCallback"
      @loadedmetadata="loadedmetadataCallback"
      @timeupdate="timeupdateCallback"
      @durationchange="durationchangeCallback"
      @waiting="waitingCallback"
      @error="errorCallback"
    ></audio>
    <div v-show="statusCtrl.showVisualization" :class="`${prefixCls}__visualization`">
      <canvas ref="canvas"
        :width="visualizedInfo.canvasWidth"
        :height="visualizedInfo.canvasHeight"
      ></canvas>
    </div>
    <div :class="[`${prefixCls}__panel`, {[`${prefixCls}__panel-collapse`] : statusCtrl.collapsePanel}]">
      <div :class="[`${prefixCls}__panel_child`, `${prefixCls}__panel_left`, `${prefixCls}__menu`]">
        <div :class="`${prefixCls}__panel_head`">
          <div class="fl">
            <i class="xvu-iconfont xvu-list"></i>
            <span>播放列表({{allAudioList.length}})</span>
          </div>
          <div :class="[`${prefixCls}__panel_btn`, 'fr']" v-show="statusCtrl.showMenuDelete" @click="menuDeleteClick">
            <i class="xvu-iconfont xvu-delete" title="清空列表"></i>
          </div>
        </div>
        <div :class="`${prefixCls}__panel_body`" @drop.prevent="drop2playlistHandler" @dragover.prevent>
          <div v-for="(v, i) in allAudioList" :key="i" @click="playItem(i)"
            :class="[`${prefixCls}__menu_item`, {active: i === statusCtrl.audioActiveIndex}]"
          >
            <div :class="`${prefixCls}__menu_item_label`">
              <div :class="`${prefixCls}__menu_item_label_title`">{{v.label || `音频 ${i + 1}`}}</div>
              <div :class="`${prefixCls}__menu_item_label_duration`">{{v.duration | formatTime}}</div>
            </div>
          </div>
          <p v-if="allAudioList.length===0" :class="`${prefixCls}__panel_body-nodata`">暂无数据</p>
        </div>
      </div>
      <div :class="[`${prefixCls}__panel_child`, `${prefixCls}__panel_right`, `${prefixCls}__lyric`]">
        <div :class="`${prefixCls}__panel_head`">
          <div :class="['fl', 'text-ellipsis']" :title="audioActive.label || `音频 ${statusCtrl.audioActiveIndex + 1}`">
            {{audioActive.label || `音频 ${statusCtrl.audioActiveIndex + 1}`}}
          </div>
          <div :class="[`${prefixCls}__panel_btn`, 'fr']" @click="panelBtnClick">
            <i class="xvu-iconfont xvu-close" title="关闭"></i>
          </div>
        </div>
        <div :class="`${prefixCls}__panel_body`">
          <p v-if="audioActive.lyric">{{audioActive.lyric}}</p>
          <p v-else :class="`${prefixCls}__panel_body-nodata`">暂无歌词</p>
        </div>
      </div>
    </div>
    <div :class="`${prefixCls}__toolsbar`">
      <div :class="`${prefixCls}__toolsbar_left`">
        <span :class="`${prefixCls}__toolsbar_left_icon`">
          <i class="xvu-iconfont xvu-prev" title="上一首" @click="prev"></i>
        </span>
        <span :class="`${prefixCls}__toolsbar_left_icon`">
          <i v-show="statusCtrl.isPlaying" class="xvu-iconfont xvu-pause" title="暂停" @click="pause"></i>
          <i v-show="!statusCtrl.isPlaying" class="xvu-iconfont xvu-play" title="播放" @click="play"></i>
        </span>
        <span :class="`${prefixCls}__toolsbar_left_icon`">
          <i class="xvu-iconfont xvu-next" title="下一首" @click="next"></i>
        </span>
      </div>
      <div :class="`${prefixCls}__toolsbar_middle`">
        <img v-show="statusCtrl.showPoster"
          :class="`${prefixCls}__toolsbar_poster`"
          :src="audioActive.poster"
          @error="onImgError"
        />
      </div>
      <div :class="`${prefixCls}__toolsbar_right`">
        <div :class="`${prefixCls}__toolsbar_row`">
          <div :class="[`${prefixCls}__toolsbar_row_left`,`${prefixCls}__toolsbar_title`]">
            <span :title="audioActive.label">{{audioActive.label || `音频 ${statusCtrl.audioActiveIndex + 1}`}}</span>
          </div>
          <div :class="[`${prefixCls}__toolsbar_row_right`]">
            <div :class="[`${prefixCls}__toolsbar_btn`, `${prefixCls}__toolsbar_volume`]" title="音量" @click.stop>
              <i :class="['xvu-iconfont', statusCtrl.audioVolume ? 'xvu-audio' : 'xvu-muted']"
                @click.stop="togglePoptip('volumebar')"
              ></i>
              <x-video-poptip :class="`${prefixCls}-poptip`" v-show="statusCtrl.poptipActive==='volumebar'">
                <x-video-progress ref="volumebarProgress" vertical @change="volumebarProgressChange"></x-video-progress>
              </x-video-poptip>
            </div>
            <div :class="[`${prefixCls}__toolsbar_btn`]" title="倍速播放" @click.stop="togglePoptip('spendlist')">
              <span class="label">
                {{(playSpendList[statusCtrl.playSpendActiveIndex] || {}).label || '速度'}}
              </span>
              <x-video-poptip :class="`${prefixCls}-poptip`" v-show="statusCtrl.poptipActive==='spendlist'">
                <div v-for="(v,i) in playSpendList" :key="i"
                  :class="[`${prefixCls}-poptip-item`, {active: i===statusCtrl.playSpendActiveIndex}]"
                  @click="spendItemClick(v,i)"
                >{{v.label}}</div>
              </x-video-poptip>
            </div>
            <div :class="[`${prefixCls}__toolsbar_btn`]" :title="playOrderActive.label" @click.stop="playOrderChange">
              <i :class="[`${prefixCls}__playorder`, 'xvu-iconfont', playOrderActive.icon]"></i>
            </div>
            <div v-show="statusCtrl.showPanel" :class="[`${prefixCls}__toolsbar_btn`]" title="打开/关闭面板" @click.stop="panelBtnClick">
              <i :class="['xvu-iconfont', 'xvu-menu']"></i>
            </div>
          </div>
        </div>
        <div :class="`${prefixCls}__toolsbar_row`">
          <div :class="[`${prefixCls}__toolsbar_row_left`,`${prefixCls}__toolsbar_progress`]">
            <x-video-progress ref="audioProgress" @change="audioProgressChange"></x-video-progress>
          </div>
          <div :class="[`${prefixCls}__toolsbar_row_right`,`${prefixCls}__toolsbar_duration`]">
            {{statusCtrl.audioCurrentTime | formatTime}} / {{statusCtrl.audioDuration | formatTime}}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import xVideoProgress from '@/components/video/src/progress.vue'
import xVideoPoptip from '@/components/video/src/poptip.vue'
import { deepCopy } from '@/script/utils.js'
const prefixCls = 'x-audio'

export default {
  name: 'x-audio',
  components: {
    xVideoProgress,
    xVideoPoptip
  },
  props: {
    size: {
      type: String,
      default: 'default' // default|large|small
    },
    options: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  data() {
    return {
      prefixCls: prefixCls,
      statusCtrl: {
        collapsePanel: true,          // 折叠播放列表
        poptipActive: '',             // 当前展示的弹窗（音量调节器/播放速度）
        isPlaying: false,             // 正在播放
        isLoading: false,             // 正在加载
        audioDuration: 0,             // 音频时长
        audioCurrentTime: 0,          // 当前进度
        audioActiveIndex: -1,         // 当前音频序号
        audioStartTime: 0,            // 音频开始时间
        audioVolume: 1,               // 音频音量
        playSpendActiveIndex: 0,      // 播放速度序号
        playOrderActiveIndex: 0,      // 播放顺序
        preload: 'auto',              // 预加载
        autoplay: false,              // 自动播放
        currentSrc: '',               // 当前音频地址
        showPanel: true,              // 是否显示播放列表&歌词
        showPoster: false,            // 是否显示poster
        showMenuDelete: false,        // 是否显示播放列表清空按钮
        showVisualization: false      // 是否显示音频可视化面板
      },
      visualizedInfo: {
        // 可视化的相关信息
        audioContext: null,
        audioSource: null,
        audioAnalyser: null,
        fftSize: 512,
        bufferLength: 256,
        dataArray: [],
        ctx: null,
        canvasWidth: 0,
        canvasHeight: 0,
        barWidth: 1,
        barColor: null
      },
      audioList: [
        // 音频列表
        // {
        //   label: 'name',        // 音频名称
        //   url: '',              // 音频地址
        //   type: '',             // 音频类型
        //   duration: 0,          // 音频时长
        //   lyric: '',            // 音频歌词
        //   poster: ''            // 音频封面
        // }
      ],
      localAudioList: [],
      playSpendList: [
        // 速度选项列表
        { label: '1.0x', value: 1.0 },
        { label: '1.5x', value: 1.5 },
        { label: '2.0x', value: 2.0 },
      ],
      playOrderList: [
        // 播放顺序列表
        { name: 'order', label: '顺序播放', value: 0, icon: 'xvu-order' },
        { name: 'loop', label: '列表循环', value: 1, icon: 'xvu-loop' },
        { name: 'loop-single', label: '单曲循环', value: 2, icon: 'xvu-loop-single' },
      ]
    }
  },
  computed: {
    allAudioList () {
      return [...this.audioList, ...this.localAudioList]
    },
    localAudioSign () {
      let result = []
      this.localAudioList.forEach(v => {
        result.push([v.label, v.size, v.type, v.lastModified].join(';'))
      })
      return result
    },
    audioActive () {
      let result = this.allAudioList[this.statusCtrl.audioActiveIndex] || {}
      return result
    },
    playOrderActive () {
      let result = this.playOrderList[this.statusCtrl.playOrderActiveIndex] || {}
      return result
    }
  },
  filters: {
    // 秒转时分秒
    formatTime (time) {
      let m = Math.floor((time / 60)) < 10 ? '0' + Math.floor((time / 60)) : Math.floor((time / 60))
      let s = Math.floor((time % 60)) < 10 ? '0' + Math.floor((time % 60)) : Math.floor((time % 60))
      time = (isNaN(m) || isNaN(s)) ? `--:--` : `${m}:${s}`
      return time
    }
  },
  watch: {
    options: {
      handler (n, o) {
        console.log(n, o)
        this.mergeAudioOptions(n)
      },
      deep: true,
      immediate: true
    }
  },
  mounted () {
    this.$nextTick(() => {
      window.addEventListener('resize', this.initCanvas)
      this.init()
    })
  },
  beforeDestroy () {
    window.removeEventListener('resize', this.initCanvas)
    this.close()
  },
  methods: {
    init () {
      // 控制条弹窗
      document.addEventListener('click', this.hidePoptip)
      // 音量初始化
      if (this.$refs.audio && this.$refs.volumebarProgress) {
        let volume = parseInt(this.options['volume'])
        if (isNaN(volume)) {
          volume = parseInt(localStorage.getItem('x-audio-volume')) || 1
        }
        this.$set(this.statusCtrl, 'audioVolume', volume)
        this.$refs.audio.volume = volume
        this.$refs.volumebarProgress.setRate(volume / 1)
      }
      // 自动播放
      if (this.allAudioList.length === 0) return
      if (this.statusCtrl.autoplay) {
        this.playItem(0)
      } else {
        this.switchItem(0)
      }
    },
    mergeAudioOptions (data) {
      Object.keys(data).forEach(k => {
        let target = ''
        switch (k) {
          case 'volume':
            target = parseInt(data[k])
            !isNaN(target) && this.$set(this.statusCtrl, 'audioVolume', target)
            break
          case 'fftSize':
            target = parseInt(data[k])
            !isNaN(target) && this.$set(this.visualizedInfo, 'fftSize', target)
            break
          case 'preload':
          case 'autoplay':
          case 'showPanel':
          case 'showPoster':
          case 'showMenuDelete':
          case 'showVisualization':
            this.$set(this.statusCtrl, k, data[k])
            break
          case 'audioList':
            this[k] = deepCopy(data[k] || [])
            this.pause()
            this.$set(this.statusCtrl, 'audioCurrentTime', 0)
            this.$set(this.statusCtrl, 'audioActiveIndex', -1)
            this.$set(this.statusCtrl, 'currentSrc', '')
            if (this.allAudioList.length > 0) {
              if (this.statusCtrl.autoplay) {
                this.playItem(0)
              } else {
                this.switchItem(0)
              }
            }
            break
          case 'playSpendList':
            this[k] = deepCopy(data[k] || [])
            break
          case 'url':
            this.$set(this.statusCtrl, 'currentSrc', data[k] || '')
            break
        }
      })
    },
    // 键盘事件
    keydownHandler (e) {
      let keyCode = e.keyCode || e.which || e.charCode
      if (!this.$refs.audio) return
      if (keyCode === 32) {
        // 空格
        this.togglePlay()
      } else if (keyCode === 37) {
        // left
        let target = Math.max(this.statusCtrl.audioCurrentTime - 10, 0)
        this.$refs.audio.currentTime = target || 0
      } else if (keyCode === 39) {
        // right
        let target = Math.min(this.statusCtrl.audioCurrentTime + 10, this.statusCtrl.audioDuration)
        this.$refs.audio.currentTime = target || 0
      }
    },
    // 图片错误处理
    onImgError (e) {
      let target = e.target || e.srcElement
      target.classList.add('error')
    },
    // 拖拽添加音频列表
    drop2playlistHandler (e) {
      let files = e.dataTransfer.files
      if (!files) return
      let localAudioList = [...this.localAudioList]
      for (let i = 0; i < files.length; i++) {
        let file = files[i]
        // 文件类型不存在即认为是文件夹或特殊文件
        if (file.type && /(.mp3|.wma|.wav|.asf|.aac|.flac|.vqf|.ape|.mid)$/i.test(file.name)) {
          window.URL = window.URL || window.webkitURL
          let url = window.URL.createObjectURL(file)
          // 去重
          let sign = [file.name, file.size, file.type, file.lastModified].join(';')
          if (!this.localAudioSign.includes(sign)) {
            localAudioList.push({
              label: file.name,
              url,
              size: file.size,
              type: file.type,
              lastModified: file.lastModified
            })
          }
        }
      }
      // 排序
      localAudioList.sort((a, b) => {
        if (a.label > b.label) return 1
        else if (a.label === b.label) return 0
        else if (a.label < b.label) return -1
      })
      this.localAudioList = localAudioList
    },
    // 展开/收起面板
    panelBtnClick () {
      this.$set(this.statusCtrl, 'collapsePanel', !this.statusCtrl.collapsePanel)
    },
    // 清空播放列表
    menuDeleteClick () {
      this.pause()
      this.pauseCallback()
      this.$set(this.statusCtrl, 'audioDuration', 0)
      this.$set(this.statusCtrl, 'audioCurrentTime', 0)
      this.$set(this.statusCtrl, 'audioActiveIndex', -1)
      this.$set(this.statusCtrl, 'currentSrc', '')
      this.localAudioList = []
      this.audioList = []
      this.$refs.audioProgress && this.$refs.audioProgress.setRate(0)
    },
    // 切换播放顺序
    playOrderChange () {
      this.$set(this.statusCtrl, 'playOrderActiveIndex', (this.statusCtrl.playOrderActiveIndex + 1) % this.playOrderList.length)
    },
    changePlayOrder (name) {
      this.playOrderList.forEach((v, i) => {
        if (v.name === name) {
          this.$set(this.statusCtrl, 'playOrderActiveIndex', i)
        }
      })
    },
    // 切换播放速度
    spendItemClick (item, index) {
      if (!this.$refs.audio) return
      this.$refs.audio.playbackRate = item.value || 1
      this.$set(this.statusCtrl, 'playSpendActiveIndex', index)
    },
    // 显示/隐藏poptip
    togglePoptip (name) {
      let target = ''
      switch (name) {
        case 'volumebar':
          if (this.statusCtrl.poptipActive === name) {
            target = this.statusCtrl.audioVolume ? 0 : 1
            this.volumebarProgressChange(target)
            this.$refs.volumebarProgress && this.$refs.volumebarProgress.setRate(target)
          } else {
            this.$set(this.statusCtrl, 'poptipActive', name)
            this.$nextTick(() => {
              this.$refs.volumebarProgress.show()
            })
          }
          break
        case 'spendlist':
          target = (this.statusCtrl.poptipActive === name) ? '' : name
          this.$set(this.statusCtrl, 'poptipActive', target)
          break
        default:
          this.$set(this.statusCtrl, 'poptipActive', '')
          break
      }
    },
    hidePoptip () {
      this.$set(this.statusCtrl, 'poptipActive', '')
    },
    // 音频音量调节
    volumebarProgressChange (rate) {
      if (!this.$refs.audio) return
      let target = parseFloat((rate * 1).toFixed(2))
      this.$refs.audio.volume = target
      this.$set(this.statusCtrl, 'audioVolume', target)
      localStorage.setItem('x-audio-volume', target)
    },
    // 调整播放位置
    audioProgressChange (rate) {
      if (!this.$refs.audio) return
      let target = Math.ceil(rate * this.statusCtrl.audioDuration)
      this.$refs.audio.currentTime = target || 0
      this.$set(this.statusCtrl, 'audioCurrentTime', target || 0)
    },
    // 切换到指定音频
    switchItem (index) {
      let item = this.allAudioList[index]
      if (!item || (this.statusCtrl.audioActiveIndex === index)) return
      this.pause()
      this.$set(this.statusCtrl, 'audioCurrentTime', 0)
      this.$set(this.statusCtrl, 'audioActiveIndex', index)
      this.$set(this.statusCtrl, 'currentSrc', item.url || '')
    },
    // 播放指定音频
    playItem (index) {
      this.switchItem(index)
      this.$nextTick(() => {
        let value = (this.playSpendList[this.statusCtrl.playSpendActiveIndex] || {}).value || 1
        this.$refs.audio && (this.$refs.audio.playbackRate = value)
        this.play()
      })
    },
    // 播放/暂停
    togglePlay () {
      if (this.statusCtrl.isPlaying) this.pause()
      else this.play()
    },
    // 播放
    play () {
      this.$refs.audio && this.$refs.audio.play()
      // 音频可视化
      if (!this.visualizedInfo.audioContext) {
        this.initAudioContext()
        this.initCanvas()
        this.renderCanvas()
      }
    },
    // 暂停
    pause () {
      this.$refs.audio && this.$refs.audio.pause()
    },
    // 上一首
    prev () {
      let len = this.allAudioList.length
      let prevIndex = (this.statusCtrl.audioActiveIndex - 1 + len) % len
      this.playItem(prevIndex)
    },
    // 下一首
    next () {
      let len = this.allAudioList.length
      let nextIndex = (this.statusCtrl.audioActiveIndex + 1 + len) % len
      this.playItem(nextIndex)
    },
    // 关闭
    close () {
      this.pause()
      this.$set(this.statusCtrl, 'audioCurrentTime', 0)
      this.$set(this.statusCtrl, 'isLoading', false)
      document.removeEventListener('click', this.hidePoptip)
    },
    // 播放事件·回调
    playCallback (e) {
      this.$set(this.statusCtrl, 'isPlaying', true)
      this.$emit('play', e)
    },
    // 暂停事件·回调
    pauseCallback (e) {
      this.$set(this.statusCtrl, 'isPlaying', false)
      this.$emit('pause', e)
    },
    // 当媒介能够开始播放时运行脚本·回调
    canplayCallback (e) {
      this.$set(this.statusCtrl, 'isLoading', false)
      this.$emit('canplay', e)
    },
    // 当媒介长度改变时运行脚本·回调
    durationchangeCallback (e) {
      let audio = e.target || e.srcElement
      this.$set(this.statusCtrl, 'audioDuration', Math.ceil(audio.duration))
      // 保存时长信息
      let len1 = this.audioList.length
      let len2 = this.localAudioList.length
      if (this.statusCtrl.audioActiveIndex < len1) {
        this.$set(this.audioList[this.statusCtrl.audioActiveIndex], 'duration', Math.ceil(audio.duration))
      } else if (this.statusCtrl.audioActiveIndex < (len1 + len2)) {
        this.$set(this.localAudioList[this.statusCtrl.audioActiveIndex - len1], 'duration', Math.ceil(audio.duration))
      }
    },
    // 当媒介改变其播放位置时运行脚本·回调
    timeupdateCallback (e) {
      let audio = e.target || e.srcElement
      if (!audio || !this.$refs.audioProgress) return
      this.$set(this.statusCtrl, 'audioCurrentTime', Math.ceil(audio.currentTime) || 0)
      let target = this.statusCtrl.audioCurrentTime / this.statusCtrl.audioDuration
      this.$refs.audioProgress.setRate(target)
    },
    // 当媒介已抵达结尾时运行脚本·回调
    endedCallback () {
      if (this.playOrderActive.name === 'loop') this.next()
      else if (this.playOrderActive.name === 'loop-single') this.play()
      else if (this.statusCtrl.audioActiveIndex < (this.allAudioList.length - 1)) this.next()
      else { this.$emit('allended') }
    },
    // 当媒介已停止播放但打算继续播放时运行脚本·回调
    waitingCallback () {
      this.$set(this.statusCtrl, 'isLoading', true)
    },
    // 当在元素加载期间发生错误时运行脚本·回调
    errorCallback (e) {
      console.log(e)
    },
    // 当媒介元素的持续时间以及其他媒介数据已加载时运行脚本·回调
    loadedmetadataCallback (e) {
      let audio = e.target || e.srcElement
      this.$set(this.statusCtrl, 'audioDuration', Math.ceil(audio.duration))
    },
    // 音频可视化·初始化audioContext
    initAudioContext () {
      if (!this.$refs.audio) return
      const AudioContext = window.AudioContext || window.webkitAudioContext
      this.visualizedInfo.audioContext = new AudioContext()
      this.visualizedInfo.audioAnalyser = this.visualizedInfo.audioContext.createAnalyser()
      this.visualizedInfo.audioAnalyser.fftSize = this.visualizedInfo.fftSize || 512
      this.visualizedInfo.audioSource = this.visualizedInfo.audioContext.createMediaElementSource(this.$refs.audio)
      this.visualizedInfo.audioSource.connect(this.visualizedInfo.audioAnalyser)
      this.visualizedInfo.audioAnalyser.connect(this.visualizedInfo.audioContext.destination)
      this.visualizedInfo.bufferLength = this.visualizedInfo.audioAnalyser.frequencyBinCount
      this.visualizedInfo.dataArray = new Uint8Array(this.visualizedInfo.bufferLength)
      this.visualizedInfo.audioAnalyser.getByteFrequencyData(this.visualizedInfo.dataArray)
    },
    // 音频可视化·初始化canvas
    initCanvas () {
      if (!this.$refs.canvas) return
      let cvsInfo = this.$refs.canvas.getBoundingClientRect()
      let bufferLength = this.visualizedInfo.audioAnalyser.frequencyBinCount
      this.visualizedInfo.ctx = this.$refs.canvas.getContext('2d')
      this.$set(this.visualizedInfo, 'canvasWidth', cvsInfo.width)
      this.$set(this.visualizedInfo, 'canvasHeight', cvsInfo.height)
      let barWidth = Math.max((cvsInfo.width - bufferLength + 1) / bufferLength, 1)
      this.$set(this.visualizedInfo, 'barWidth', barWidth)
      let barColor = this.visualizedInfo.ctx.createLinearGradient(0, 0, 0, cvsInfo.height)
      barColor.addColorStop(0, 'red')
      barColor.addColorStop(0.5, 'yellow')
      barColor.addColorStop(1, 'green')
      this.$set(this.visualizedInfo, 'barColor', barColor)
    },
    // 音频可视化·绘制
    renderCanvas () {
      requestAnimationFrame(this.renderCanvas)
      if (!this.statusCtrl.isPlaying || !this.statusCtrl.showVisualization) return
      let canvas = this.$refs.canvas
      let bufferLength = this.visualizedInfo.bufferLength
      let barWidth = this.visualizedInfo.barWidth
      let barColor = this.visualizedInfo.barColor
      this.visualizedInfo.ctx.clearRect(0, 0, canvas.width, canvas.height)
      this.visualizedInfo.audioAnalyser.getByteFrequencyData(this.visualizedInfo.dataArray)
      this.visualizedInfo.ctx.fillStyle = barColor
      for (let i = 0, x = 0; i < bufferLength; i++) {
        let barHeight = this.visualizedInfo.dataArray[i] / 256 * canvas.height
        this.visualizedInfo.ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight)
        x += barWidth + 1
      }
    }
  }
}
</script>
